package nachos.threads;

import java.rmi.dgc.DGC;
import java.util.LinkedList;

import nachos.machine.*;   

/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>,
 * and multiple threads can be waiting to <i>listen</i>. But there should never
 * be a time when both a speaker and a listener are waiting, because the two
 * threads can be paired off at this point.
 */
public class Communicator {
    /**
     * Allocate a new communicator.
     */
	
	private Lock lock;
	private Condition2 waitForSpeaker; //queue of listeners waiting for a speaker
	private Condition2 waitForListener;  //queue of speakers waiting for a listener
	private Condition2 waitForMessage;  // listener waiting for a message
	private Condition2 waitForEmptyMailbox;  //speaker waiting for empty buffer
	private Condition2 waitForRCPT;  //speaker waiting for message to be heard
	private Condition2 waitToSpeak;  //queue of speakers waiting their turn
    private Condition2 waitToListen;  //queue of listeners waiting their turn;
	private int listeners;
	private int speakers;
	int message;
	private boolean messageWaiting;
	private static final char dbgComm = 'C';
	
		
    public Communicator() {
    	lock = new Lock();
    	waitForSpeaker = new Condition2(lock);
    	waitForListener	= new Condition2(lock);
    	waitForEmptyMailbox = new Condition2(lock);
    	waitForMessage = new Condition2(lock);
    	waitForRCPT = new Condition2(lock);
    	waitToSpeak = new Condition2(lock);
    	waitToListen = new Condition2(lock);
    }

    /**
     * Wait for a thread to listen through this communicator, and then transfer
     * <i>word</i> to the listener.
     *
     * <p>
     * Does not return until this thread is paired up with a listening thread.
     * Exactly one listener should receive <i>word</i>.
     * We will allow only one speaker at a time to speak. If there is already a thread
     * trying to speak, the next thread will wait its turn. After the current speaker
     * receives a notification that the message was received by the listener,
     * it will wake up the next speaker in line.
     * </p>
     *
     * @param	word	the integer to transfer.
     */
    public void speak(int word) {
    	lock.acquire();
    	speakers++;
    	Lib.debug(dbgComm, KThread.currentThread().getName() + " starting to speak " + word);
    	//if there is more than one speaker, somebody is already
    	//speaking, so wait
    	if(speakers > 1){
    		waitToSpeak.sleep();
    	}
    	
    	if(listeners == 0){  //wait for a listener 	
      		if(messageWaiting){  //there is no listener, and previous message
      							//not picked up
    			waitForEmptyMailbox.sleep();
    		}else{  // previous message picked up, but no listener
    			waitForListener.sleep();
    		}
       	}else{
       		waitForSpeaker.wake();
       		if(messageWaiting){  //there is a listener, but previous message
       							//not picked up. Wait till he picks it up
       			waitForEmptyMailbox.sleep();
       		}
    	}
    	message = word;  //set the message to the word from param
    	Lib.debug(dbgComm, KThread.currentThread().getName() + " said " + message);
    	messageWaiting = true;  // set the MWI indicator
    	waitForMessage.wake();  // wake up a listener waiting for a message
    	waitForRCPT.sleep();   // wait for the notification that the listener
    						// picked up a message
    	speakers--; 
    	waitToSpeak.wake();  //wake up the next speaker waiting to speak
      	Lib.debug(dbgComm, KThread.currentThread().getName() + " finished speaking");
    	lock.release();	
    }

    /**
     * Wait for a thread to speak through this communicator, and then return
     * the <i>word</i> that thread passed to <tt>speak()</tt>.
     * 
     *  <p>
     * We will allow only one thread at a time to listen. If there is
     * a thread already listening, the next listener will wait before even checking
     * if a speaker or message are present. When the listener is done, it will
     * wake up the next listener thread waiting to listen before returning. 
     * </p>
     *
     * @return	the integer transferred.
     */    
    public int listen() {
    	
    	int retVal = 0;
    	lock.acquire();
    	Lib.debug(dbgComm, KThread.currentThread().getName() + " starting to listen");
    	listeners++;  
    	if(listeners > 1){  //if there is more than 1 listener, somebody is already
    						// picking up the msg, wait
    		waitToListen.sleep();
    	}
    	
    	if(speakers == 0){  
    		if(!messageWaiting){  //no speakers and no msg present, wait for the msg
       			waitForMessage.sleep();
       		}else{
       			waitForSpeaker.sleep();// no speakers but the previous msg still
       									//not picked up, wait for speaker
       		}
    	}else{
    		waitForListener.wake();  //there is a speaker waiting, wake him up
       		if(!messageWaiting){  //if there is no message waiting, sleep
       			waitForMessage.sleep();
       		}
    	}
    	
    	retVal = message;
    	messageWaiting = false;
    	listeners--;
    	waitForEmptyMailbox.wake();  //wake up a speaker waiting for msg to be picked up
    	waitForRCPT.wake();  //send a confirmation to the speaker
    	waitToListen.wake();  //wake up the next listener in line
    	Lib.debug(dbgComm, KThread.currentThread().getName() + " picking up " + message);
    	    		
    	lock.release();
    	return retVal;
    	
    }
    
    
    private static class Speaker implements Runnable {
    	Speaker(Communicator com, int which) {
    	    this.com = com;
    	    this.which = which;
    	}
    	
    	public void run() {
    		int counter = which*10;
    	    for (int i=counter; i< counter+5; i++) {
    	    	//System.out.println(which + " wants to say " + i);
    	    	com.speak(i);
    	    	//System.out.println(which + " finished saying " + i);

    	    }
    	    
    	}

    	private Communicator com;
    	private int which;
        }
    
    private static class Listener implements Runnable {
    	Listener (Communicator com, int which) {
    	    this.com = com;
    	    this.which = which;
    	}
    	
    	public void run() {
    	    for (int i=0; i<5; i++) {
    	    //	System.out.println(which + " Starting to listen ");
    	    	int message = com.listen();
    	    //	System.out.println(which + " Just Heard " + message);

    		
    	    }
    	}

    	private Communicator com;
    	private int which;
        }
    public static void selfTest(){
    	Communicator com = new Communicator();
    	new KThread(new Listener(com, 1)).setName("ListenerThree").fork();
    	//new KThread(new Speaker(com, 1)).setName("speakerOne").fork();
    	new KThread(new Speaker(com, 2)).setName("speakerTwo").fork();
    	new KThread(new Speaker(com, 3)).setName("speakerThree").fork();
    	//new KThread(new Speaker(com, 4)).setName("speakerFour").fork();
    	new KThread(new Listener(com, 1)).setName("ListenerOne").fork();
    	new KThread(new Listener(com, 2)).setName("ListenerTwo").fork();
    	//new Listener(com,3).run();
    	new Speaker(com,1).run();
    }
}
